package com.elite.security.web;

import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.Filter;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.web.*;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.firewall.*;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;

import java.util.ArrayList;
import java.util.List;

public class CustomWebSecurity extends AbstractConfiguredSecurityBuilder<Filter, CustomWebSecurity>
		implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
	private final Log logger = LogFactory.getLog(getClass());

	private final List<RequestMatcher> ignoredRequests = new ArrayList<>();

	private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();

	private IgnoredRequestConfigurer ignoredRequestRegistry;

	private HttpFirewall httpFirewall;

	private RequestRejectedHandler requestRejectedHandler;
	private RouterSingleFilterChain routerSingleFilterChain;

	private boolean debugEnabled;

	private WebInvocationPrivilegeEvaluator privilegeEvaluator;

	private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;

	private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();

	private SecurityExpressionHandler<FilterInvocation> expressionHandler = this.defaultWebSecurityExpressionHandler;

	private Runnable postBuildAction = () -> {
	};

	private ServletContext servletContext;

	/**
	 * Creates a new instance
	 * @param objectPostProcessor the {@link ObjectPostProcessor} to use
	 * @see CustomWebSecurityConfiguration
	 */
	public CustomWebSecurity(ObjectPostProcessor<Object> objectPostProcessor,RouterSingleFilterChain routerSingleFilterChain) {
		super(objectPostProcessor);
		this.routerSingleFilterChain=routerSingleFilterChain;
	}

	public IgnoredRequestConfigurer ignoring() {
		return this.ignoredRequestRegistry;
	}

	/**
	 * Allows customizing the {@link HttpFirewall}. The default is
	 * {@link StrictHttpFirewall}.
	 * @param httpFirewall the custom {@link HttpFirewall}
	 * @return the {@link CustomWebSecurity} for further customizations
	 */
	public CustomWebSecurity httpFirewall(HttpFirewall httpFirewall) {
		this.httpFirewall = httpFirewall;
		return this;
	}

	public CustomWebSecurity debug(boolean debugEnabled) {
		this.debugEnabled = debugEnabled;
		return this;
	}


	public CustomWebSecurity addSecurityFilterChainBuilder(
			SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
		// 添加过滤器链
		this.securityFilterChainBuilders.add(securityFilterChainBuilder);
		return this;
	}

	/**
	 * Set the {@link WebInvocationPrivilegeEvaluator} to be used. If this is not
	 * specified, then a {@link RequestMatcherDelegatingWebInvocationPrivilegeEvaluator}
	 * will be created based on the list of {@link SecurityFilterChain}.
	 * @param privilegeEvaluator the {@link WebInvocationPrivilegeEvaluator} to use
	 * @return the {@link CustomWebSecurity} for further customizations
	 */
	public CustomWebSecurity privilegeEvaluator(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
		this.privilegeEvaluator = privilegeEvaluator;
		return this;
	}

	/**
	 * Set the {@link SecurityExpressionHandler} to be used. If this is not specified,
	 * then a {@link DefaultWebSecurityExpressionHandler} will be used.
	 * @param expressionHandler the {@link SecurityExpressionHandler} to use
	 * @return the {@link CustomWebSecurity} for further customizations
	 */
	public CustomWebSecurity expressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
		Assert.notNull(expressionHandler, "expressionHandler cannot be null");
		this.expressionHandler = expressionHandler;
		return this;
	}

	/**
	 * Gets the {@link SecurityExpressionHandler} to be used.
	 * @return the {@link SecurityExpressionHandler} for further customizations
	 */
	public SecurityExpressionHandler<FilterInvocation> getExpressionHandler() {
		return this.expressionHandler;
	}

	/**
	 * Gets the {@link WebInvocationPrivilegeEvaluator} to be used.
	 * @return the {@link WebInvocationPrivilegeEvaluator} for further customizations
	 */
	public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
		return this.privilegeEvaluator;
	}

	/**
	 * Executes the Runnable immediately after the build takes place
	 * @param postBuildAction
	 * @return the {@link CustomWebSecurity} for further customizations
	 */
	public CustomWebSecurity postBuildAction(Runnable postBuildAction) {
		this.postBuildAction = postBuildAction;
		return this;
	}

	/**
	 * Sets the handler to handle
	 * {@link org.springframework.security.web.firewall.RequestRejectedException}
	 * @param requestRejectedHandler
	 * @return the {@link CustomWebSecurity} for further customizations
	 * @since 5.7
	 */
	public CustomWebSecurity requestRejectedHandler(RequestRejectedHandler requestRejectedHandler) {
		Assert.notNull(requestRejectedHandler, "requestRejectedHandler cannot be null");
		this.requestRejectedHandler = requestRejectedHandler;
		return this;
	}

	@Override
	protected Filter performBuild() throws Exception {
		Assert.state(!this.securityFilterChainBuilders.isEmpty(),
				() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
						+ "Typically this is done by exposing a SecurityFilterChain bean. "
						+ "More advanced users can invoke " + CustomWebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");


		int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();

		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);

		List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();

		// 遍历忽略那些请求
		for (RequestMatcher ignoredRequest : this.ignoredRequests) {
			CustomWebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
					+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");

			SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);

			securityFilterChains.add(securityFilterChain);

			requestMatcherPrivilegeEvaluatorsEntries.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
		}



		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {

			// 构建过滤器链
			SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
			// 添加过滤器链
			securityFilterChains.add(securityFilterChain);

			requestMatcherPrivilegeEvaluatorsEntries.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
		}

		if (this.privilegeEvaluator == null) {
			this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(requestMatcherPrivilegeEvaluatorsEntries);
		}

		routerSingleFilterChain.buildFilterChain(securityFilterChains);

		// 创建过滤器链代理对象
		CustomFilterChainProxy filterChainProxy = new CustomFilterChainProxy(routerSingleFilterChain);


//		if (this.httpFirewall != null) {
//			filterChainProxy.setFirewall(this.httpFirewall);
//		}
//		if (this.requestRejectedHandler != null) {
//			filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
//		}
//		else if (!this.observationRegistry.isNoop()) {
//
//			CompositeRequestRejectedHandler requestRejectedHandler = new CompositeRequestRejectedHandler(
//					new ObservationMarkingRequestRejectedHandler(this.observationRegistry),
//					new HttpStatusRequestRejectedHandler());
//			filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
//		}
//
//
//		filterChainProxy.setFilterChainDecorator(getFilterChainDecorator());
//		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;

		this.postBuildAction.run();
		return result;
	}

	private RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> getRequestMatcherPrivilegeEvaluatorsEntry(SecurityFilterChain securityFilterChain) {
		List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = new ArrayList<>();
		for (Filter filter : securityFilterChain.getFilters()) {

			if (filter instanceof AuthorizationFilter) {
				AuthorizationManager<HttpServletRequest> authorizationManager = ((AuthorizationFilter) filter).getAuthorizationManager();
				AuthorizationManagerWebInvocationPrivilegeEvaluator evaluator = new AuthorizationManagerWebInvocationPrivilegeEvaluator(authorizationManager);
				evaluator.setServletContext(this.servletContext);
				privilegeEvaluators.add(evaluator);
			}
		}
		return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
		try {
			this.defaultWebSecurityExpressionHandler.setRoleHierarchy(applicationContext.getBean(RoleHierarchy.class));
		} catch (NoSuchBeanDefinitionException ex) {
		}
		try {
			this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(PermissionEvaluator.class));
		} catch (NoSuchBeanDefinitionException ex) {
		}
		this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
		try {
			this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
		} catch (NoSuchBeanDefinitionException ex) {
		}
		try {
			this.requestRejectedHandler = applicationContext.getBean(RequestRejectedHandler.class);
		} catch (NoSuchBeanDefinitionException ex) {
		}
		try {
			this.observationRegistry = applicationContext.getBean(ObservationRegistry.class);
		} catch (NoSuchBeanDefinitionException ex) {
		}
	}

	@Override
	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

	FilterChainProxy.FilterChainDecorator getFilterChainDecorator() {

		return new FilterChainProxy.VirtualFilterChainDecorator();

	}

	/**
	 * Allows registering {@link RequestMatcher} instances that should be ignored by
	 * Spring Security.
	 *
	 * @author Rob Winch
	 * @since 3.2
	 */
	public class IgnoredRequestConfigurer extends AbstractRequestMatcherRegistry<IgnoredRequestConfigurer> {

		IgnoredRequestConfigurer(ApplicationContext context) {
			setApplicationContext(context);
		}

		@Override
		protected IgnoredRequestConfigurer chainRequestMatchers(List<RequestMatcher> requestMatchers) {
			CustomWebSecurity.this.ignoredRequests.addAll(requestMatchers);
			return this;
		}

		/**
		 * Returns the {@link CustomWebSecurity} to be returned for chaining.
		 */
		public CustomWebSecurity and() {
			return CustomWebSecurity.this;
		}

	}
}
